﻿// 2020/7/1: 首个版本
// 2020/8/26: 支持v3
// 2020/11/6: 新增气压、相对湿度、能见度、风向、风速、PM10、道路状况、车道交通状况、灾害情况等字段。道路交通状况更名
// 2021/7/7: 新增envSampleProtocols
// 2023/8/17: 新增瞬时雨量字段
// 2023/10/10: 补充部分英文注释
// 2023/10/24: 新增字段（累计雨量、精确风向、总辐射强度、紫外线辐射强度、臭氧浓度、一氧化碳浓度、二氧化硫浓度、二氧化氮浓度）
// 2025/3/26: 支持插值功能
// 2025/4/30: 增加envSampleConverters
// 2025/7/21: 修正speedLimit插值

var WeatherType = {
    Unknown : 0, // 未知
    Sunny : 1, // 晴
    Cloudy : 2, // 阴
    Rainy : 3, // 雨
    Snowy : 4, // 雪
    Foggy : 5, // 雾
    Sand : 6, // 沙尘
};

var RoadType = {
    Unknown : 0, // 未知
    Highway : 1, // Without traffic light / 高速路，无红绿灯
    CityExpress : 2, // Without traffic light / 城市快速路，无红绿灯
    CityMain : 3, // With traffic light / 城市主干道，有红绿灯
    GeneralRoad : 4, // With traffic light / 一般道路，有红绿灯
    CountryRoad : 5, // Without traffic light / 乡村道路，无红绿灯
};

var SpotType = {
    Unknown : 0,  // 未知
    TollGate : 1, // 收费站
    ServiceArea : 2, // 服务区
    Tunnel : 3, // 隧道
    Accident : 4, // 事故
    Crossing : 5, // Intersection / 路口
    Roundabout : 7, // 环岛
    HighwayEntrance : 8, // 高速入口
    HighwayExit : 9, // 高速出口
    Bridge : 10, // 桥
    ExpressEntrance : 11, // 快速路入口
    ExpressExit : 12, // 快速路出口
};

function Spot() {
    this.type = SpotType.Unknown; // 场所类型
    this.distance = 0; // [m] Distance between the spot and subject vehicle / 本车到场所的距离
};

var TrafficStatus = {
    Unknown : 0, // 未知
    Open : 1, // 畅通
    Slow : 2, // 缓慢
    Crowded: 3, // 拥挤
    Reverse: 4, // Only for adjacent lane traffic / 逆向（仅限相邻车道交通状况）
};

var WindDirection = {
    Unknown: 0,  // 未知
    East: 1, // 东风
    South: 2, // 南风
    West: 3, // 西风
    North: 4, // 北风
    SouthEast: 5, // 东南风
    NorthEast: 6, // 东北风
    SouthWest: 7, // 西南风
    NorthWest: 8, // 西北风
};

var RoadSurfaceStatus = {
    Unknown: 0,  // 未知
    Dry: 1, // 干燥
    Wet: 2, // 湿滑
    SnowCovered: 3, // 积雪
    Frozen: 4, // 结冰
};

var DisasterStatus = {
    Unknown: 0,  // 未知
    No: 1, // 无灾害
    Yes: 2, // 有灾害
};

function EnvSample(session, time) {
    this.session = session; // Date or AppoxSessionIdentifier
    this.time = time; // [s] Time offset in session / Session内的相对时间
    this.weatherType = WeatherType.Unknown; // 天气类型
    this.temperature = null; // [°C] 温度
    this.humidity = null; // [%] Relative humidity / 相对湿度
    this.pressure = null; // [mb] Atmospheric pressure / 气压
    this.pm25 = null; // [ug/m3] PM 2.5
    this.pm10 = null; // [ug/m3] PM 10
    this.o3Concentration = null; // [ppb] Ozone concentration / 臭氧浓度
    this.coConcentration = null; // [ppb] Carbon monoxide concentration / 一氧化碳浓度
    this.so2Concentration = null; // [ppb] Sulfur dioxide concentration / 二氧化硫浓度
    this.no2Concentration = null; // [ppb] Nitrogen dioxide concentration / 二氧化氮浓度
    this.visibility = null; // [KM] 能见度
    this.illumination = null; // [lux] 光照强度
    this.totalRadiationIntensity = null; // [W/m2] 总辐射强度
    this.ultravioletRadiationIntensity = null; // [W/m2] 紫外线辐射强度
    this.rainfall = null; // [mm/min] Instantaneous rainfall / 瞬时雨量
    this.cumulativeRainfall = null; // [mm] 累计雨量
    this.windSpeed = null; // [m/s] 风速
    this.accurateWindDirection = null; // [deg] Accurate wind direction (from), 0 is north wind, CCW is positive, ranges -180~180 / 北风为0，CCW为正，范围在-180～180度
    this.windDirection = WindDirection.Unknown; // Coarse wind direction (from) / 大致风向
    this.typhoonStatus = DisasterStatus.Unknown; // 台风状况
    this.tornadoStatus = DisasterStatus.Unknown; // 龙卷风状况
    this.lightningStatus = DisasterStatus.Unknown; // 雷电状况
    this.hailStatus = DisasterStatus.Unknown; // 冰雹状况
    this.roadName = null; // 道路名称
    this.speedLimit = null; // [KPH] 道路限速
    this.roadType = RoadType.Unknown; // 道路类型
    this.roadSurfaceStatus = RoadSurfaceStatus.Unknown; // 路面状况
    this.roadTraffic = TrafficStatus.Unknown; // 道路交通状况
    this.currentLaneTraffic = TrafficStatus.Unknown; // 本车道交通状况
    this.leftLaneTraffic = TrafficStatus.Unknown; // 左车道交通状况
    this.rightLaneTraffic = TrafficStatus.Unknown; // 右车道交通状况
    this.spots = []; // 场所列表
}

function convEnvSampleV2(gs) {
    if (gs.values.length < 2) return null;

    var roadNameSize = gs.values[0] == null ? 0 : gs.values[0];
    var spotCount = gs.values[1] == null ? 0 : gs.values[1];
    if (gs.values.length != 10 + 2 * spotCount + roadNameSize) return null;

    var sample = new EnvSample(gs.session, gs.time);
    if (gs.values[2] != null) sample.weatherType = gs.values[2];
    sample.temperature = gs.values[3];
    sample.pm25 = gs.values[4];
    sample.speedLimit = gs.values[5];
    if (gs.values[6] != null) sample.roadType = gs.values[6];
    sample.illumination = gs.values[7];
    sample.windSpeed = gs.values[8];
    if (gs.values[9] != null) sample.roadTraffic = gs.values[9];

    for (var i = 0; i < spotCount; i++) {
        if (gs.values[10 + 2 * i] == null || gs.values[11 + 2 * i] == null) continue;
        var spot = new Spot();
        spot.type = gs.values[10 + 2 * i];
        spot.distance = gs.values[11 + 2 * i];
        sample.spots.push(spot);
    }

    if (roadNameSize > 0) {
        var roadName = '';
        var roadNameBase = 10 + 2 * spotCount;
        for (var i = 0; i < roadNameSize; i++) {
            var code = gs.values[roadNameBase + i];
            if ((240 & code) == 240) {
                var code1 = gs.values[roadNameBase + i + 1],
                code2 = gs.values[roadNameBase + i + 2],
                code3 = gs.values[roadNameBase + i + 3];
                roadName += String.fromCodePoint(((code & 7) << 18) | ((code1 & 63) << 12) | ((code2 & 63) << 6) | (code3 & 63));
                i += 3;
            } else if ((224 & code) == 224) {
                var code1 = gs.values[roadNameBase + i + 1],
                code2 = gs.values[roadNameBase + i + 2];
                roadName += String.fromCodePoint(((code & 15) << 12) | ((code1 & 63) << 6) | (code2 & 63));
                i += 2;
            } else if ((192 & code) == 192) {
                var code1 = gs.values[roadNameBase + i + 1];
                roadName += String.fromCodePoint(((code & 31) << 6) | (code1 & 63));
                i++;
            } else if ((128 & code) == 0) {
                roadName += String.fromCharCode(code);
            }
        }
        sample.roadName = roadName;
    }

    return sample;
}

function convEnvSampleV3(gs) {
    if (gs.values.length < 2) return null;

    var roadNameSize = gs.values[0] == null ? 0 : gs.values[0];
    var spotCount = gs.values[1] == null ? 0 : gs.values[1];
    if (gs.values.length != 64 + 2 * spotCount + roadNameSize) return null;

    var sample = new EnvSample(gs.session, gs.time);
    if (gs.values[2] != null) sample.weatherType = gs.values[2];
    sample.temperature = gs.values[3];
    sample.pm25 = gs.values[4];
    sample.speedLimit = gs.values[5];
    if (gs.values[6] != null) sample.roadType = gs.values[6];
    sample.illumination = gs.values[7];
    sample.windSpeed = gs.values[8];
    if (gs.values[9] != null) sample.roadTraffic = gs.values[9];
    sample.pressure = gs.values[11];
    sample.humidity = gs.values[12];
    sample.visibility = gs.values[13];
    if (gs.values[14] != null) sample.windDirection = gs.values[14];
    sample.pm10 = gs.values[15];
    if (gs.values[16] != null) sample.roadSurfaceStatus = gs.values[16];
    if (gs.values[17] != null) sample.currentLaneTraffic = gs.values[17];
    if (gs.values[18] != null) sample.leftLaneTraffic = gs.values[18];
    if (gs.values[19] != null) sample.rightLaneTraffic = gs.values[19];
    if (gs.values[20] != null) sample.typhoonStatus = gs.values[20];
    if (gs.values[21] != null) sample.tornadoStatus = gs.values[21];
    if (gs.values[22] != null) sample.lightningStatus = gs.values[22];
    if (gs.values[23] != null) sample.hailStatus = gs.values[23];
    sample.rainfall = gs.values[24];
    sample.cumulativeRainfall = gs.values[25];
    sample.accurateWindDirection = gs.values[26];
    sample.totalRadiationIntensity = gs.values[27];
    sample.ultravioletRadiationIntensity = gs.values[28];
    sample.o3Concentration = gs.values[29];
    sample.coConcentration = gs.values[30];
    sample.so2Concentration = gs.values[31];
    sample.no2Concentration = gs.values[32];

    for (var i = 0; i < spotCount; i++) {
        if (gs.values[64 + 2 * i] == null || gs.values[65 + 2 * i] == null) continue;
        var spot = new Spot();
        spot.type = gs.values[64 + 2 * i];
        spot.distance = gs.values[65 + 2 * i];
        sample.spots.push(spot);
    }

    if (gs.values[10] != null) {
        sample.roadName = gs.values[10];
    } else if (roadNameSize > 0) {
        var roadName = '';
        var roadNameBase = 64 + 2 * spotCount;
        for (var i = 0; i < roadNameSize; i++) {
            var code = gs.values[roadNameBase + i];
            if ((240 & code) == 240) {
                var code1 = gs.values[roadNameBase + i + 1],
                code2 = gs.values[roadNameBase + i + 2],
                code3 = gs.values[roadNameBase + i + 3];
                roadName += String.fromCodePoint(((code & 7) << 18) | ((code1 & 63) << 12) | ((code2 & 63) << 6) | (code3 & 63));
                i += 3;
            } else if ((224 & code) == 224) {
                var code1 = gs.values[roadNameBase + i + 1],
                code2 = gs.values[roadNameBase + i + 2];
                roadName += String.fromCodePoint(((code & 15) << 12) | ((code1 & 63) << 6) | (code2 & 63));
                i += 2;
            } else if ((192 & code) == 192) {
                var code1 = gs.values[roadNameBase + i + 1];
                roadName += String.fromCodePoint(((code & 31) << 6) | (code1 & 63));
                i++;
            } else if ((128 & code) == 0) {
                roadName += String.fromCharCode(code);
            }
        }
        sample.roadName = roadName;
    }

    return sample;
}

// Interpolator / 样本插值函数
function interpolateEnvSample(session, time, s1, w1, s2, w2) {
    var sample = new EnvSample(session, time);

    sample.weatherType = w1 > w2 ? s1.weatherType : s2.weatherType;
    sample.roadType = w1 > w2 ? s1.roadType : s2.roadType;
    sample.windDirection = w1 > w2 ? s1.windDirection : s2.windDirection;
    sample.roadSurfaceStatus = w1 > w2 ? s1.roadSurfaceStatus : s2.roadSurfaceStatus;
    sample.roadTraffic = w1 > w2 ? s1.roadTraffic : s2.roadTraffic;
    sample.currentLaneTraffic = w1 > w2 ? s1.currentLaneTraffic : s2.currentLaneTraffic;
    sample.leftLaneTraffic = w1 > w2 ? s1.leftLaneTraffic : s2.leftLaneTraffic;
    sample.rightLaneTraffic = w1 > w2 ? s1.rightLaneTraffic : s2.rightLaneTraffic;
    sample.typhoonStatus = w1 > w2 ? s1.typhoonStatus : s2.typhoonStatus;
    sample.tornadoStatus = w1 > w2 ? s1.tornadoStatus : s2.tornadoStatus;
    sample.lightningStatus = w1 > w2 ? s1.lightningStatus : s2.lightningStatus;
    sample.hailStatus = w1 > w2 ? s1.hailStatus : s2.hailStatus;
    sample.roadName = w1 > w2 ? s1.roadName : s2.roadName;
    sample.speedLimit = w1 > w2 ? s1.speedLimit : s2.speedLimit;
    sample.spots = w1 > w2 ? s1.spots : s2.spots;

    sample.temperature = s1.temperature == null || s2.temperature == null ? null : s1.temperature * w1 + s2.temperature * w2;
    sample.humidity = s1.humidity == null || s2.humidity == null ? null : s1.humidity * w1 + s2.humidity * w2;
    sample.pressure = s1.pressure == null || s2.pressure == null ? null : s1.pressure * w1 + s2.pressure * w2;
    sample.pm25 = s1.pm25 == null || s2.pm25 == null ? null : s1.pm25 * w1 + s2.pm25 * w2;
    sample.pm10 = s1.pm10 == null || s2.pm10 == null ? null : s1.pm10 * w1 + s2.pm10 * w2;
    sample.o3Concentration = s1.o3Concentration == null || s2.o3Concentration == null ? null : s1.o3Concentration * w1 + s2.o3Concentration * w2;
    sample.coConcentration = s1.coConcentration == null || s2.coConcentration == null ? null : s1.coConcentration * w1 + s2.coConcentration * w2;
    sample.so2Concentration = s1.so2Concentration == null || s2.so2Concentration == null ? null : s1.so2Concentration * w1 + s2.so2Concentration * w2;
    sample.no2Concentration = s1.no2Concentration == null || s2.no2Concentration == null ? null : s1.no2Concentration * w1 + s2.no2Concentration * w2;
    sample.visibility = s1.visibility == null || s2.visibility == null ? null : s1.visibility * w1 + s2.visibility * w2;
    sample.illumination = s1.illumination == null || s2.illumination == null ? null : s1.illumination * w1 + s2.illumination * w2;
    sample.totalRadiationIntensity = s1.totalRadiationIntensity == null || s2.totalRadiationIntensity == null ? null : s1.totalRadiationIntensity * w1 + s2.totalRadiationIntensity * w2;
    sample.ultravioletRadiationIntensity = s1.ultravioletRadiationIntensity == null || s2.ultravioletRadiationIntensity == null ? null : s1.ultravioletRadiationIntensity * w1 + s2.ultravioletRadiationIntensity * w2;
    sample.rainfall = s1.rainfall == null || s2.rainfall == null ? null : s1.rainfall * w1 + s2.rainfall * w2;
    sample.cumulativeRainfall = s1.cumulativeRainfall == null || s2.cumulativeRainfall == null ? null : s1.cumulativeRainfall * w1 + s2.cumulativeRainfall * w2;
    sample.windSpeed = s1.windSpeed == null || s2.windSpeed == null ? null : s1.windSpeed * w1 + s2.windSpeed * w2;
    sample.accurateWindDirection = s1.accurateWindDirection == null || s2.accurateWindDirection == null ? null : s1.accurateWindDirection * w1 + s2.accurateWindDirection * w2;

    return sample;
}

// Supported protocols / 环境信息样本支持解析的协议
var envSampleProtocols = [
    "env-sample-v2",
    "env-sample-v3",
];

// Converters / 通用样本转换函数表
var envSampleConverters = {
    "env-sample-v2": convEnvSampleV2,
    "env-sample-v3": convEnvSampleV3,
};

// Use this function to query sample at target time / 获取指定session指定时刻最近的环境信息样本
function getEnvSample(session, time, interpolated) {
    var s1 = null, w1 = null, s2 = null, w2 = null;
    var pairV3 = biGetGeneralSamplePair('env-sample-v3', null, session, time, 1);
    var pairV2 = biGetGeneralSamplePair('env-sample-v2', null, session, time, 1);
    
    if (pairV3 != null) {
        if (pairV3.sample1 != null) s1 = convEnvSampleV3(pairV3.sample1);
        if (pairV3.sample2 != null) s2 = convEnvSampleV3(pairV3.sample2);
        w1 = pairV3.weight1;
        w2 = pairV3.weight2;
    }
    else if (pairV2 != null) {
        if (pairV2.sample1 != null) s1 = convEnvSampleV2(pairV2.sample1);
        if (pairV2.sample2 != null) s2 = convEnvSampleV2(pairV2.sample2);
        w1 = pairV2.weight1;
        w2 = pairV2.weight2;
    }
    else return null;
    
    if (s1 == null || s2 == null) {
        return s1 == null ? s2 : s1;
    }
    
    if (interpolated) {
        return interpolateEnvSample(session, time, s1, w1, s2, w2);
    }
    else {
        return w1 > w2 ? s1 : s2;
    }
}